#!/usr/bin/env bash
# Copyright (c) 2021 maminjie <canpool@163.com>
# SPDX-License-Identifier: MulanPSL-2.0


method_def ship

usage_ship() {
    module_usage "ship" "A package ship query tool"
}

do_ship() {
    module_do "ship" "$@"
}

ship_usage_help() {
    module_usage_help ship
}

# ship_do_help subcmd
ship_do_help() {
    module_do_help ship "$1"
}

# __ship_get_subcmd cmd
#   Get the fullname of cmd
# Returns:
#   "" or fullname
__ship_get_subcmd() {
    local cmd=""
    case "${1}" in
        "h"|"help")
            cmd="help"
            ;;
        "pro"|"provides")
            cmd="provides"
            ;;
        "req"|"requires")
            cmd="requires"
            ;;
        "wpro"|"whatprovides")
            cmd="whatprovides"
            ;;
        "wreq"|"whatrequires")
            cmd="whatrequires"
            ;;
        "reqb"|"requiredby")
            cmd="requiredby"
            ;;
        "bdep"|"builddep")
            cmd="builddep"
            ;;
        *)
            ;;
    esac
    echo "$cmd"
}

# ship_get_bins_fromobs prj pkg
ship_get_bins_fromobs() {
    local bins=""
    local prj=$1
    local pkg=$2
    local results=$(osc results "$prj" "$pkg" 2> /dev/null)
    local repo_arch=$(echo "$results" | awk '{print $1"/"$2}' | grep "aarch64") # default is aarch64
    if [ -n "$repo_arch" ]; then
        # /build/project/repo/arch/package
        local content="/build/$prj/$repo_arch/$pkg"
        bins=$(osc api -X GET "$content" 2> /dev/null | grep ".rpm" | awk '{print $2}' | awk -F '=' '{print $2}' | sed 's/"//g' | sed 's/\.rpm$//g' | uniq)
        for bin in $bins; do
            if [[ "$bin" =~ \.src$ ]]; then
                echo "$bin"
            else
                echo "$(dnf_get_pkgname "$bin")"
            fi
        done
    fi
}

__ship_usage_params() {
    echo "params:"
    echo "    ... - dnf global options, like: --repo reponame"
}

# __ship_pkgs_do func prj pkg ...
# __ship_pkgs_do func prj/pkg ...
# __ship_pkgs_do func pkgs ...
__ship_pkgs_do() {
    local pkgs=""
    local prj=""
    local pkg=""
    local arg1=""
    local func="$1"; shift 1
    if [ $# -ge 2 ]; then
        # Quotation marks cannot be added to regular expressions
        if [[ "$2" =~ ^- ]]; then
            arg1="$1"; shift 1
        else
            prj=$1; pkg=$2; shift 2
        fi
    else
        arg1="$1"; shift 1
    fi
    dnf_set_global_option "$@"
    if [ -n "$prj" ]; then
        pkgs=$(ship_get_bins_fromobs "$prj" "$pkg")
    else
        # prj/pkg
        eval $(echo "$arg1" | awk -F "/" '{
            if (NF >= 2) {
                printf("prj=%s;pkg=%s;", $1, $2)
            }
        }')
        if [ -n "$prj" ]; then
            pkgs=$(ship_get_bins_fromobs "$prj" "$pkg")
        else
            pkgs=$(get_list "$arg1")
        fi
    fi
    $func "$pkgs"
}


ship_usage_provides() {
    echo "provides (pro): Display capabilities provided by the package"
    echo ""
    echo "usage:"
    echo "    ${PROG} ship pro PROJECT PACKAGE ...      # obs PROJECT and PACKAGE"
    echo "    ${PROG} ship pro PROJECT/PACKAGE ...      # obs PROJECT and PACKAGE"
    echo "    ${PROG} ship pro PACKAGEs ...             # rpm PACKAGE names"
    echo ""
    __ship_usage_params
    echo ""
    echo "examples:"
    echo "    1) Iterate the rpm packages under openEuler:Mainline/dnf (aarch64 default)"
    echo ""
    echo "        ${PROG} ship pro openEuler:Mainline dnf --repo mainline"
    echo "        ${PROG} ship pro openEuler:Mainline/dnf --repo mainline"
    echo ""
    echo "    2) Display dnf's sub-package python3-dnf"
    echo ""
    echo "        ${PROG} ship pro python3-dnf --repo mainline"
    echo ""
    echo "    3) Display dnf's sub-package python3-dnf and dnf"
    echo ""
    echo "        ${PROG} ship pro \"python3-dnf dnf\" --repo mainline"
    echo ""
    echo "    4) Display dnf's sub-packages those are in file (one per line or space separated)"
    echo ""
    echo "        ${PROG} ship pro xx.txt --repo mainline"
    echo ""
}

# __ship_do_provides pkgs
__ship_do_provides() {
    OLD_IFS=$IFS
    IFS=$'\n'
    printf "%s,%s\n" "# Package" "# Provides"
    for pkg in $pkgs; do
        local pros=$(dnf_get_provides "$pkg")
        for pro in $pros; do
            printf "%s,%s\n" "$pkg" "$pro"
        done
    done
    IFS=$OLD_IFS
}

# ship_do_provides pkgs ...
# ship_do_provides prj pkg ...
# ship_do_provides prj/pkg ...
ship_do_provides() {
    if [ $# -lt 1 ]; then
        ship_usage_provides; exit
    fi
    __ship_pkgs_do __ship_do_provides "$@" | format_column ','
}

ship_usage_requires() {
    echo "requires (req): Display capabilities that the package depends on"
    echo ""
    echo "usage:"
    echo "    ${PROG} ship req PROJECT PACKAGE ...   # obs PROJECT and PACKAGE"
    echo "    ${PROG} ship req PROJECT/PACKAGE ...   # obs PROJECT and PACKAGE"
    echo "    ${PROG} ship req PACKAGEs ...          # rpm PACKAGE names"
    echo ""
    __ship_usage_params
    echo ""
    echo "notes:"
    echo "    The \"# Package\" column is xxx.src means buildRequires, otherwise means installRequires"
    echo ""
}

# __ship_do_requires pkgs
__ship_do_requires() {
    local pkgs="$1"
    local srcs=()
    printf "%s,%s,%s,%s\n" "# Package" "# Requires" "# Requires-bin" "# Requires-src"
    for pkg in $pkgs; do
        local reqs=$(dnf_get_requires "$pkg")
        for req in $reqs; do
            local bins=$(dnf_get_whatprovides "$req")
            for bin in $bins; do
                local src="Not-Found"
                if [ -z "$bin" ]; then
                    bin="Not-Found"
                else
                    if [[ "$bin" =~ \.src$ ]]; then
                        src=$(dnf_get_pkgname "$bin")
                    else
                        bin=$(dnf_get_pkgname "$bin")
                        src=$(dnf_get_source "$bin")
                    fi
                    if [ -z "$src" ]; then
                        src=$bin
                    fi
                fi
                printf "%s,%s,%s,%s\n" "$pkg" "$req" "$bin" "$src"
                array_add srcs "$src"
            done
        done
    done
    printf ",,,\n"
    echo "# Requires Package"
    array_uniq srcs
}

# ship_do_requires prj pkg ...
# ship_do_requires prj/pkg ...
# ship_do_requires pkgs ...
ship_do_requires() {
    if [ $# -lt 1 ]; then
        ship_usage_requires; exit
    fi
    __ship_pkgs_do __ship_do_requires "$@" # | format_column ','
}

ship_usage_whatprovides() {
    echo "whatprovides (wpro): Show only results that provide REQ"
    echo ""
    echo "usage:"
    echo "    ${PROG} ship wpro REQ ..."
    echo ""
    __ship_usage_params
    echo ""
}

# ship_do_whatprovides req ...
ship_do_whatprovides() {
    local req=""
    if [ $# -lt 1 ]; then
        ship_usage_whatprovides; exit
    else
        req=$1; shift 1
    fi
    dnf_set_global_option "$@"
    local bins=$(dnf_get_whatprovides "$req")
    if [ -z "$bins" ]; then
        echo "Not Found package that provides \"$req\""; exit
    fi
    {
    printf "%s,%s,%s\n" "# Provides" "# Binary" "# Source"
    for bin in $bins; do
        local src=""
        if [[ "$bin" =~ \.src$ ]]; then
            src=$(dnf_get_pkgname "$bin")
        else
            bin=$(dnf_get_pkgname "$bin")
            src=$(dnf_get_source "$bin")
        fi
        printf "%s,%s,%s\n" "$req" "$bin" "$src"
    done
    } | format_column ','
}

ship_usage_whatrequires() {
    echo "whatrequires (wreq): Shows results that requires package provides and files"
    echo ""
    echo "usage:"
    echo "    ${PROG} ship wreq REQ ..."
    echo ""
    __ship_usage_params
    echo ""
}

# ship_do_whatrequires req
ship_do_whatrequires() {
    local req=""
    if [ $# -lt 1 ]; then
        ship_usage_whatrequires; exit
    else
        req=$1; shift 1
    fi
    dnf_set_global_option "$@"
    local bins=$(dnf_get_whatrequires "$req")
    {
        printf "%s,%s,%s\n" "# Symbol" "# Requires-bin" "# Requires-src"
        for bin in $bins; do
            local src=""
            if [[ "$bin" =~ \.src$ ]]; then
                src=$(dnf_get_pkgname "$bin")
            else
                bin=$(dnf_get_pkgname "$bin")
                src=$(dnf_get_source "$bin")
            fi
            printf "%s,%s,%s\n" "$req" "$bin" "$src"
        done
    } | format_column ','
}


ship_usage_requiredby() {
    echo "requiredby (reqb): Display capabilities that the package is required by"
    echo ""
    echo "usage:"
    echo "    ${PROG} ship reqb PROJECT PACKAGE ...    # obs PROJECT and PACKAGE"
    echo "    ${PROG} ship reqb PROJECT/PACKAGE ...    # obs PROJECT and PACKAGE"
    echo "    ${PROG} ship reqb PACKAGEs ...           # rpm PACKAGE names"
    echo ""
    __ship_usage_params
    echo ""
    echo "examples:"
    echo "    1) Iterate the rpm packages under openEuler:Mainline/dnf (aarch64 default)"
    echo ""
    echo "        ${PROG} ship reqb openEuler:Mainline dnf --repo mainline"
    echo "        ${PROG} ship reqb openEuler:Mainline/dnf --repo mainline"
    echo ""
    echo "    2) Analyze dnf's sub-package python3-dnf"
    echo ""
    echo "        ${PROG} ship reqb python3-dnf --repo mainline"
    echo ""
    echo "    3) Analyze dnf's sub-package python3-dnf and dnf"
    echo ""
    echo "        ${PROG} ship reqb \"python3-dnf dnf\" --repo mainline"
    echo ""
    echo "    4) Analyze dnf's sub-packages those are in file (one per line or space separated)"
    echo ""
    echo "        ${PROG} ship reqb xx.txt --repo mainline"
    echo ""
    echo "format:"
    echo "    One way to format analysis results is:"
    echo "        1) ${PROG} ship reqb python3-dnf --repo mainline | tee res.txt"
    echo "        2) cat res.txt | column -t -s ','"
    echo ""
    echo "notes:"
    echo "    The \"# RequiredBy-bin\" column is xxx.src means buildRequiredby, otherwise means installRequiredby"
    echo ""
}

# __ship_do_requiredby pkgs
__ship_do_requiredby() {
    local pkgs="$1"
    local rbpkgs=()

    OLD_IFS=$IFS
    IFS=$'\n'
    printf "%s,%s,%s,%s\n" "# Package" "# Provides" "# RequiredBy-bin" "# RequiredBy-src"
    for pkg in $pkgs; do
        local pros=$(dnf_get_provides "$pkg")
        for pro in $pros; do
            local reqbins=$(dnf_get_whatrequires "$pro")
            for bin in $reqbins; do
                local reqsrc=""
                if [[ "$bin" =~ \.src$ ]]; then
                    reqsrc=$(dnf_get_pkgname "$bin")
                else
                    bin=$(dnf_get_pkgname "$bin")
                    reqsrc=$(dnf_get_source "$bin")
                fi
                if [ -z "$reqsrc" ]; then
                    continue
                fi
                printf "%s,%s,%s,%s\n" "$pkg" "$pro" "$bin" "$reqsrc"
                array_add rbpkgs "$reqsrc"
            done
        done
    done
    IFS=$OLD_IFS
    printf ",,,\n"
    printf "# RequiredBy:${#rbpkgs[@]} \n"
    array_uniq rbpkgs
}

# ship_do_requiredby prj pkg ...
# ship_do_requiredby prj/pkg ...
# ship_do_requiredby pkgs ...
ship_do_requiredby() {
    if [ $# -lt 1 ]; then
        ship_usage_requiredby; exit
    fi
    __ship_pkgs_do __ship_do_requiredby "$@" # | format_column ','
}

# ___ship_do_installdep ia oa
___ship_do_installdep() {
    local iarray=($(array_all $1))
    local oarray=$2

    while [ ${#iarray[@]} != 0 ]; do
        local deps=()
        for pkg in ${iarray[@]}; do
            local srcs=()
            local syms=$(dnf_get_requires "$pkg")
            for sym in $syms; do
                local bin=$(dnf_get_whatprovides "$sym")
                local src=""
                if [ -z "$bin" ]; then
                    continue
                else
                    if [[ "$bin" =~ \.src$ ]]; then
                        src=$(dnf_get_pkgname "$bin")
                    else
                        bin=$(dnf_get_pkgname "$bin")
                        src=$(dnf_get_source "$bin")
                    fi
                    if [ -z "$src" ]; then
                        src=$bin
                    fi
                fi
                printf "%s,%s,%s,%s\n" "$pkg" "$sym" "$bin" "$src"
                array_add srcs "$src"
            done
            array_extend deps srcs
        done
        iarray=($(array_uniq deps))
        local tmp=()
        for pkg in ${iarray[@]}; do
            if [ $(array_exists $oarray $pkg) ]; then
                continue
            fi
            tmp=(${tmp[@]} ${array[$j]})
        done
        if [ ${#tmp[@]} != 0 ]; then
            array_extend $oarray tmp
        fi
        iarray=(${tmp[@]})
    done
}

# __ship_do_builddep ia oa
__ship_do_builddep() {
    local iarray=($(array_all "$1"))
    local oarray="$2"
    local deps=()
    for ((i = 0; i < ${#iarray[@]}; i++)); do
        local pkg=${iarray[$i]}
        local srcs=()
        local syms=$(dnf_get_requires "$pkg")
        for sym in $syms; do
            local bin=$(dnf_get_whatprovides "$sym")
            local src=""
            if [ -z "$bin" ]; then
                continue
            else
                if [[ "$bin" =~ \.src$ ]]; then
                    src=$(dnf_get_pkgname "$bin")
                else
                    bin=$(dnf_get_pkgname "$bin")
                    src=$(dnf_get_source "$bin")
                fi
                if [ -z "$src" ]; then
                    src=$bin
                fi
            fi
            printf "%s,%s,%s,%s\n" $pkg $sym $bin $src
            array_add srcs $src
        done
        array_extend deps srcs
    done
    deps=($(array_uniq deps))
    array_extend $oarray deps
}

# __ship_get_repo_opts repoarray
__ship_get_repo_opts() {
    local repos=($(array_all $1))
    local opts=""
    for repo in ${repos[@]}; do
        if [ -z "$opts" ]; then
            opts="--repo $repo"
        else
            opts="$opts --repo $repo"
        fi
    done
    echo "$opts"
}

ship_usage_builddep() {
    echo "builddep (bdep): Display package build dependencies and their requires"
    echo ""
    echo "usage:"
    echo "    ${PROG} ship bdep PACKAGES -b|--bin reponame -s|--src reponame ..."
    echo ""
}

# ship_do_builddep pkgs -b|--bin reponame -s|--src reponame ...
ship_do_builddep() {
    if [ $# -lt 1 ]; then
        ship_usage_builddep; exit
    fi
    local ARGS=$(getopt -o "b:s:" -l "bin:,src:" -n "builddep/bdep" -- "$@")
    eval set -- "${ARGS}"

    local binrepos=()
    local srcrepos=()
    while true; do
        case "${1}" in
            -b|--bin)
                shift; array_add binrepos ${1}; shift
                ;;
            -s|--src)
                shift; array_add srcrepos ${1}; shift
                ;;
            --)
                shift; break;
            ;;
        esac
    done

    local pkgs=$(get_list "$1")
    local array=()
    local pkgship=()
    for pkg in $pkgs; do
        array=(${array[@]} $pkg)
    done

    printf "%s,%s,%s,%s\n" "# Package" "# Requires-sym" "# Requires-bin" "# Requires-src"
    local srcopts=$(__ship_get_repo_opts srcrepos)
    dnf_set_global_option "$srcopts"
    __ship_do_builddep array pkgship
    printf "%s,%s,%s,%s\n" "------" "------" "------" "------"
    local binopts=$(__ship_get_repo_opts binrepos)
    dnf_set_global_option "$binopts"
    array_extend array pkgship
    array=($(array_uniq array))
    ___ship_do_installdep array pkgship
    printf "\nDependencies:\n"
    array_uniq pkgship
}

